package com.changgou.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.changgou.goods.feign.SkuFeign;
import com.changgou.goods.pojo.Sku;
import com.changgou.search.dao.SkuEsMapper;
import com.changgou.search.pojo.SkuInfo;
import com.changgou.search.service.SkuEsService;
import entity.Result;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * @author ：hushengjin
 */
@Service
public class SkuEsServiceImpl implements SkuEsService {

    @Autowired
    private SkuFeign skuFeign;
    @Autowired
    private SkuEsMapper skuEsMapper;
    @Autowired
    private ElasticsearchTemplate esTemplate;

    /**
     * 导入Sku
     */
    @Override
    public void importSku() {
        //调用changgou-service-goods微服务
        Result<List<Sku>> skuListResult = skuFeign.findByStatus("1");
        //将数据转成search.sku
        List<SkuInfo> skuInfos = JSON.parseArray(JSON.toJSONString(skuListResult.getData()), SkuInfo.class);
        for (SkuInfo skuInfo : skuInfos) {
            //将spec里的参数转换成Map集合
            Map<String, Object> specMap = JSON.parseObject(skuInfo.getSpec());
            skuInfo.setSpecMap(specMap);
        }
        //批量增加到es库中
        skuEsMapper.saveAll(skuInfos);
    }

    /**
     * 搜索
     *
     * @param searchMap
     * @return
     */
    @Override
    public Map search(Map<String, String> searchMap) {

        //构建搜索条件
        NativeSearchQueryBuilder builder = builderBasicQuery(searchMap);

        //搜索列表
        Map<String, Object> resultMap = searchList(builder);

        //聚合查询
        resultMap.putAll(groupList(builder, searchMap));

        return resultMap;
    }

    /**
     * 将所有规格数据转入到Map中
     *
     * @param specList
     * @return
     */
    public Map<String, Set<String>> specPutAll(List<String> specList) {
        //新建一个集合
        Map<String, Set<String>> specMap = new HashMap<String, Set<String>>();

        //遍历集合数据,存入到新建的集合中
        for (String specString : specList) {
            //将specString转成map集合
            Map<String, String> map = JSON.parseObject(specString, Map.class);
            //循环转换后的map
            for (Map.Entry<String, String> entry : map.entrySet()) {
                String key = entry.getKey();  //规格名字
                String value = entry.getValue();  //规格选项值
                Set<String> specValues = specMap.get(key);
                if (specValues == null) {
                    //新建一个set
                    specValues = new HashSet<String>();
                }
                //将当前的规格加入到set集合中
                specValues.add(value);
                //将数据存入带specMap中
                specMap.put(key, specValues);
            }
        }
        return specMap;
    }

    /**
     * 分组搜索实现
     *
     * @param builder
     * @return
     */
    public Map groupList(NativeSearchQueryBuilder builder, Map<String, String> searchMap) {

        /**
         * 根据分类名字|品牌名字|规格进行分组查询
         * 1:给分组查询取别名
         * 2:指定分组查询的域
         */
        //规格查询spec
        builder.addAggregation(AggregationBuilders.terms("skuSpec").field("spec.keyword"));

        //category
        if (searchMap == null || StringUtils.isEmpty(searchMap.get("category"))) {
            builder.addAggregation(AggregationBuilders.terms("skuCategory").field("categoryName"));
        }

        //brand
        if (searchMap == null || StringUtils.isEmpty(searchMap.get("brand"))) {
            builder.addAggregation(AggregationBuilders.terms("skuBrand").field("brandName"));
        }

        //执行搜索
        AggregatedPage<SkuInfo> skuInfoPage = esTemplate.queryForPage(builder.build(), SkuInfo.class);
        //获取所有分组查询数据
        Aggregations aggregations = skuInfoPage.getAggregations();
        //存储所有数据
        Map groupMap = new HashMap();

        //获取所有规格数据
        List<String> specList = getGroupData(aggregations, "skuSpec");
        Map<String, Set<String>> specMap = specPutAll(specList);
        groupMap.put("specList", specMap);

        //分类分组数据
        if (searchMap == null || StringUtils.isEmpty(searchMap.get("category"))) {
            List<String> categoryList = getGroupData(aggregations, "skuCategory");
            groupMap.put("categoryList", categoryList);
        }

        //品牌分组数据
        if (searchMap == null || StringUtils.isEmpty(searchMap.get("brand"))) {
            List<String> brandList = getGroupData(aggregations, "skuBrand");
            groupMap.put("brandList", brandList);
        }

        return groupMap;

    }

    /***
     * 获取分组数据抽取
     *
     * @param aggregations
     * @param fieldName
     * @return
     */
    public List<String> getGroupData(Aggregations aggregations, String fieldName) {

        //根据别名获取分组数据
        StringTerms terms = aggregations.get(fieldName);

        //创建一个集合，从StringTerms获取每个分组数据
        List<String> dataList = new ArrayList<String>();
        for (StringTerms.Bucket bucket : terms.getBuckets()) {
            dataList.add(bucket.getKeyAsString());
        }
        return dataList;
    }

    /**
     * 数据搜索
     *
     * @param builder
     * @return
     */
    private Map<String, Object> searchList(NativeSearchQueryBuilder builder) {
        //用于储存返回的结果
        Map<String, Object> resultMap = new HashMap<String, Object>();
        //开启高亮配置
        HighlightBuilder.Field field = new HighlightBuilder
                .Field("name")  //高亮区域
                .preTags("<em style='color:red;'>")  //前缀
                .postTags("</em>")  //后缀
                .fragmentSize(100);  //高亮字段长度
        //添加高亮
        builder.withHighlightFields(field);
        //查询解析器
        NativeSearchQuery nativeSearchQuery = builder.build();
        //结果解析,分页搜索
        AggregatedPage<SkuInfo> skuPage = esTemplate.queryForPage(nativeSearchQuery, SkuInfo.class, new SearchResultMapper() {
            /***
             * 数据+分页
             * @param searchResponse:查询之后的数据
             * @param aClass
             * @param pageable:分页数据的封装
             * @param <T>
             * @return
             */
            @Override
            public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
                //创建一个集合储存高亮数据
                List<T> list = new ArrayList<T>();
                //获取所有数据,循环
                for (SearchHit hit : searchResponse.getHits()) {
                    //获取非高亮数据
                    String json = hit.getSourceAsString();
                    SkuInfo skuInfo = JSON.parseObject(json, SkuInfo.class);
                    /***
                     * 获取高亮数据
                     * hit.getHighlightFields():获取所有高亮域的数据
                     * get("name"):获取name域的高亮数据
                     */
                    HighlightField highlightField = hit.getHighlightFields().get("name");
                    if (highlightField != null) {
                        //创建StringBuffer储存高亮数据
                        StringBuffer buffer = new StringBuffer();
                        for (Text text : highlightField.getFragments()) {
                            buffer.append(text.toString());
                        }
                        //将非高亮数据替换为高亮数据
                        skuInfo.setName(buffer.toString());
                    }
                    list.add((T)skuInfo);
                }
                /***
                 * 1:高亮集合数据
                 * 2:分页对象
                 * 3:总记录数
                 */
                return new AggregatedPageImpl<T>(list, pageable, searchResponse.getHits().getTotalHits());
            }
        });
        resultMap.put("rows", skuPage.getContent());
        resultMap.put("totalPages", skuPage.getTotalPages());
        resultMap.put("total", skuPage.getTotalElements());

        //获取分页数据
        //当前页
        resultMap.put("pageNum", nativeSearchQuery.getPageable().getPageNumber() + 1);
        //每页条数
        resultMap.put("pageSize", nativeSearchQuery.getPageable().getPageSize());
        return resultMap;
    }

    /**
     * 构建基本查询
     *
     * @param searchMap
     * @return
     */
    private NativeSearchQueryBuilder builderBasicQuery(Map<String, String> searchMap) {
        //准备搜索条件
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();

        //多条件组合
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        if (searchMap != null) {
            //获取关键词，判断关键词是否为空,不为空，则将搜索的关键词作为搜索条件
            if (!StringUtils.isEmpty(searchMap.get("keywords"))) {
                //nativeSearchQueryBuilder.withQuery(QueryBuilders.matchQuery("name", searchMap.get("keywords")));
                boolQueryBuilder.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")));
            }
            //分类category不为空, 用户选中了分类
            if (!StringUtils.isEmpty(searchMap.get("category"))) {
                boolQueryBuilder.must(QueryBuilders.matchQuery("categoryName", searchMap.get("category")));
            }
            //品牌brand不为空, 用户选中了品牌
            if (!StringUtils.isEmpty(searchMap.get("brand"))) {
                boolQueryBuilder.must(QueryBuilders.matchQuery("brandName", searchMap.get("brand")));
            }
            //价格范围查找range
            if (!StringUtils.isEmpty(searchMap.get("price"))) {
                String[] prices = searchMap.get("price").replace("元", "").replace("以上", "").split("-");
                boolQueryBuilder.must(QueryBuilders.rangeQuery("price").gt(Integer.parseInt(prices[0])));
                if (prices.length == 2) {
                    boolQueryBuilder.must(QueryBuilders.rangeQuery("price").lte(Integer.parseInt(prices[1])));
                }
            }
            //规格检索
            for (Map.Entry<String, String> entry : searchMap.entrySet()) {
                String key = entry.getKey();
                if (key.startsWith("spec_")) {
                    boolQueryBuilder.must(QueryBuilders.matchQuery("specMap." + key.substring(5) + ".keyword", entry.getValue()));
                }
            }
            //排序
            String sortRule = searchMap.get("sortRule");
            String sortField = searchMap.get("sortField");
            if (!StringUtils.isEmpty(sortField)) {
                nativeSearchQueryBuilder
                        .withSort(
                                SortBuilders.fieldSort(sortField)  //排序的域
                                        .order(SortOrder.valueOf(sortRule))  //排序的规则
                        );
            }
        }

        //分页实现
        Integer page =converter(searchMap);
        //每页显示的条数
        Integer size = 30;
        nativeSearchQueryBuilder.withPageable(PageRequest.of(page - 1, size));

        //设置组合条件
        nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
        return nativeSearchQueryBuilder;
    }

    public Integer converter(Map<String, String> searchMap) {
        if (searchMap != null && !StringUtils.isEmpty(searchMap.get("pageNum"))) {

            try {
                //获取分页,数据转换
                return Integer.parseInt(searchMap.get("pageNum"));
            } catch (NumberFormatException e) {
                e.printStackTrace();
            }
        }

        return 1;
    }
}
